/**
 * Candidate number: 35834
 */
package tv.starfu.game.states;

import com.artemis.Entity;
import com.artemis.EntitySystem;
import com.artemis.SystemManager;
import com.artemis.World;
import com.jme3.app.Application;
import com.jme3.app.SimpleApplication;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.asset.AssetManager;
import com.jme3.font.BitmapFont;
import com.jme3.font.BitmapText;
import com.jme3.input.InputManager;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.debug.Arrow;
import com.jme3.system.AppSettings;
import com.jme3.util.SkyFactory;
import com.jme3.bullet.BulletAppState;
import com.jme3.post.FilterPostProcessor;
import com.jme3.scene.shape.Box;
import com.jme3.texture.Texture;
import net.beadsproject.beads.core.AudioContext;
import net.beadsproject.beads.core.Bead;
import net.beadsproject.beads.ugens.Clock;
import tv.starfu.components.level.KyberneteGrid;
import tv.starfu.game.EntityFactory;
import tv.starfu.sequencer.components.AmpEnvelope;
import tv.starfu.sequencer.components.PitchEnvelope;
import tv.starfu.sequencer.components.SamplePlayback;
import tv.starfu.sequencer.components.TimeVector;
import tv.starfu.sequencer.model.Sequencer;
import tv.starfu.sequencer.model.SequencerModel;
import tv.starfu.systems.InputSystem;
import tv.starfu.systems.PhysicsSystem;
import tv.starfu.systems.RenderSystem;
import tv.starfu.systems.SampleEventSystem;
import tv.starfu.systems.DrumSequencerSystem;
import tv.starfu.systems.level.KyberTileSystem;
import tv.starfu.utils.Music;

public class ArcadeState extends AbstractAppState implements AppState {

    private Node arcadeRootNode = new Node("arcadeRootNode");
    private Node arcadeGuiNode = new Node("arcadeGuiNode");

    private SimpleApplication app;
    private AssetManager assetManager;
    private ViewPort guiViewport;
    private ViewPort viewport;
    private InputManager inputManager;
    private AppSettings settings;
    private AppStateManager stateManager;
    private BulletAppState bullet;
    
    private DirectionalLight sunshine;
    private AudioContext ac;  
    private World world;
    
    private EntitySystem renderSystem;
    private EntitySystem controllerSystem;
    private EntitySystem inputSystem;
    private EntitySystem drumSequencerSystem;
    
    private boolean isRunning;
    
    private Clock clock;
    private Bead clockTower;

    /* CLOCK */
    private int absTick, absBeat, absBar = 0;
    private int curTick, curBeat, curBar = 0;
    public TimeVector now;
    private float totalTime;

    /**
     * Create Arcade state
     * @param app
     * @param settings 
     */
    public ArcadeState( SimpleApplication   app, 
                        World               world, 
                        AudioContext        ac) 
    {    
        this.app            = (SimpleApplication)app;
       // this.guiNode        = app.getGuiNode();
       // this.rootNode       = app.getRootNode();
        this.assetManager   = app.getAssetManager();
        this.inputManager   = app.getInputManager();
        this.viewport       = app.getViewPort();
        this.guiViewport    = app.getGuiViewPort();
        this.stateManager   = app.getStateManager();
        this.bullet         = this.stateManager.getState(BulletAppState.class);
        
       // this.settings       = settings;
        this.world          = world;
        this.ac             = ac;
      }
    

    /**
     * Initialise Arcade State.
     * Create Player / Level Entities.
     * @param stateManager
     * @param app 
     */
    @Override
    public void initialize(AppStateManager stateManager, Application app) {
        super.initialize(stateManager, app);
        this.app = (SimpleApplication)app;

        // init stuff that is independent of whether state is PAUSED or RUNNING
        setupGlobalClock();
        
        FilterPostProcessor processor = (FilterPostProcessor) assetManager.loadAsset("Shaders/newfilter.j3f");
        app.getViewPort().addProcessor(processor);
        
        SystemManager systemManager = world.getSystemManager();
        
        drumSequencerSystem = systemManager.setSystem(new DrumSequencerSystem(ac, now));
        //inputSystem         = systemManager.setSystem(new InputSystem(inputManager));
       // controllerSystem       = systemManager.setSystem(new ControllerSystem(this));  
        renderSystem        = systemManager.setSystem(new RenderSystem(this.app, bullet, arcadeRootNode));
        
        systemManager.initializeAll();        
        
        drawHUD();
        
        
        /* ENTITIES */
        createArcadePlayer();
        createTerrain();
        //skyFactory();
        
        /* VISUAL DEBUG */
        attachCoordinateAxes(new Vector3f(0, 0, 0));
       // attachGrid(new Vector3f(0, -2f, 0), 700, ColorRGBA.LightGray);
    }


    @Override
    public void cleanup() {
        // unregister all my listeners, detach all my nodes, etc...
        super.cleanup();
        
        app.setDisplayFps(false);
        app.setDisplayStatView(false);
        
            this.app.getRootNode().attachChild(arcadeRootNode);
            this.app.getGuiNode().attachChild(arcadeGuiNode);
        
        ac.stop();
    }
    
    @Override
    public void setEnabled(boolean enabled) {
        super.setEnabled(enabled);
        if (enabled) {
            isRunning = true;
            app.setDisplayFps(true);
            app.setDisplayStatView(true);

            this.app.getRootNode().attachChild(arcadeRootNode);
            this.app.getGuiNode().attachChild(arcadeGuiNode);

        }
        else {
            // Game is paused
            isRunning = false;
            app.setDisplayFps(false);
            app.setDisplayStatView(false);
        
            this.app.getRootNode().detachChild(arcadeRootNode);
            this.app.getGuiNode().detachChild(arcadeGuiNode);

        }
    }
    
    /**
     * Sequentially process Systems
     * @param tpf 
     */
    @Override
    public void update(float tpf) {
        totalTime += tpf;
        
        
        world.loopStart();
        world.setDelta((int) tpf);      // time since last loop?
        
        //inputSystem.process();
        if(isRunning) {
        clock.pause(false);
        now.setTime(curBar, curBeat, curTick);
        drumSequencerSystem.process();
        renderSystem.process();  
        }
        else {
           pauseClock();
        }
    }
    
    
    /**
     * Create new player entity (position, direction)
     */
    private void createArcadePlayer() {
        Entity e = EntityFactory.createArcadeStarRacer(world);
        e.refresh();
    }

    private void createTerrain() {
        Entity e = EntityFactory.createTerrain(world);
        e.refresh();
    }    

    /**
     * Creates skybox.
     */
    private void skyFactory() {
        Texture west    = assetManager.loadTexture("Textures/skyboxes/moon/moon_base1.jpg");
        Texture east    = assetManager.loadTexture("Textures/skyboxes/moon/moon_base2.jpg");
        Texture north   = assetManager.loadTexture("Textures/skyboxes/moon/moon_base3.jpg");
        Texture south   = assetManager.loadTexture("Textures/skyboxes/moon/moon_base4.jpg");
        Texture up      = assetManager.loadTexture("Textures/skyboxes/moon/moon_base5.jpg");
        Texture down    = assetManager.loadTexture("Textures/skyboxes/moon/moon_base6.jpg");
        arcadeRootNode.attachChild(SkyFactory.createSky(assetManager, west, east, north, south, up, down));  
    }
    
    /**
     * Draw HUD and attach to localGuiNode
     * @return BitmapText
     */
    public BitmapText drawHUD() {
        BitmapFont guiFont = assetManager.loadFont(
                "Interface/Fonts/Default.fnt");
        BitmapText displaytext = new BitmapText(guiFont);
        displaytext.setSize(guiFont.getCharSet().getRenderedSize());
        displaytext.move(200, displaytext.getLineHeight() + 20, 0);
        displaytext.setText("Welcome to the ARCADE!!! (Do not) Press SHIFT to get to the Grid.");
        arcadeGuiNode.attachChild(displaytext);
        return displaytext;
    }

    
    /**
     * Visual Debug : show axes
     * @param pos 
     */
    private void attachCoordinateAxes(Vector3f pos) {
        Arrow arrow = new Arrow(Vector3f.UNIT_X);
        arrow.setLineWidth(4); // make arrow thicker
        putShape(arrow, ColorRGBA.Red).setLocalTranslation(pos);

        arrow = new Arrow(Vector3f.UNIT_Y);
        arrow.setLineWidth(4); // make arrow thicker
        putShape(arrow, ColorRGBA.Green).setLocalTranslation(pos);

        arrow = new Arrow(Vector3f.UNIT_Z);
        arrow.setLineWidth(4); // make arrow thicker
        putShape(arrow, ColorRGBA.Blue).setLocalTranslation(pos);
    }

    private Geometry putShape(Mesh shape, ColorRGBA color) {
        Geometry g = new Geometry("coordinate axis", shape);
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat.getAdditionalRenderState().setWireframe(true);
        mat.setColor("Color", color);
        g.setMaterial(mat);
        arcadeRootNode.attachChild(g);
        return g;
    }

    /**
     * Visual Debug : attach 2d grid
     * @param pos
     * @param size
     * @param color 
     */
    private void attachGrid(Vector3f pos, int size, ColorRGBA color) {
        Geometry g = new Geometry("wireframe grid", new Box(100f, 0.1f, 1000f));
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        Texture tRacetrack = assetManager.loadTexture("Textures/terrain/grid.jpg");
        tRacetrack.setWrap(Texture.WrapMode.Repeat);
        mat.setTexture("ColorMap", tRacetrack);
       // mat.getAdditionalRenderState().setWireframe(true);
        mat.setColor("Color", color);
        g.setMaterial(mat);
        g.center().move(pos);
        arcadeRootNode.attachChild(g);

    }
    
     /**
     * 
     */
    private void setupGlobalClock() {
        
        clock = new Clock(ac, Music.tempo);
        clock.setTicksPerBeat(16);
        clock.setClick(true);
        
        now = new TimeVector(curBar, curBeat, curTick);
        
        clockTower = new Bead() {
            @Override
            public void messageReceived(Bead message) {
                absTick = clock.getInt();
                curTick = absTick % 16;
                
               // System.out.println("Absolute Tick: " + absTick);
               // System.out.println("Current Tick: " + curTick);

                if(curTick == 0) { 
                    absBeat = (absBeat + 1); }
                curBeat = absBeat % 16;
                
              //  System.out.println("Absolute Beat: " + absBeat);
              //  System.out.println("Current Beat: " + curBeat);

                if(clock.isBeat() && curBeat == 0) { 
                    absBar++; }                
                curBar = absBar;
                
              //  System.out.println("Absolute Bar: " + absBar);
              //  System.out.println("Current Bar: " + curBar);
                
                if(clock.isBeat()) {  System.out.println(now.toString()); }
                now.setTime(curBar, curBeat, curTick);
            }
        } ;
        
        clock.addMessageListener(clockTower);
        ac.out.addDependent(clock);        
        ac.start();
    
    }
    
   public void pauseClock() {
        if(!clock.isPaused()) {
            clock.pause(true);
        }   
    }    

    
}
